#include "./cell.hpp"
#include "yocto/string/vfs-utils.hpp"
#include "yocto/ios/ocstream.hpp"
#include "yocto/string/conv.hpp"
#include "yocto/code/utils.hpp"
#include "yocto/eta.hpp"
#include "yocto/duration.hpp"

static void save_header( const Cell &cell, const string &filename )
{
    ios::ocstream fp( filename, false );
    fp("#t");
    fp(" Em");
    fp(" pH");
    fp(" P_CO2");
    cell.sol.save_header(fp);
    fp("\n");
}

static void save_values( const Cell &cell, const string &filename, double t )
{
    ios::ocstream fp( filename, true );
    fp("%.15g",t/60.0);
    fp(" %.15g", cell.Em);
    fp(" %.15g", cell.sol.pH());
    fp(" %.15g", cell.P_CO2(t) * 760);
    cell.sol.save_values(fp);
    fp("\n");
}


static inline
void progress_callback( const eta &ETA, const double t )
{
    static const char   ch[] = { '|', '/', '-', '\\' };
    static const size_t nc   = sizeof(ch)/sizeof(ch[0]); 
    static size_t       ic   = 0;
    const duration      done( ETA.time_done );
    const duration      left( ETA.time_left );
    
    
    fprintf( stderr, "[%c]t=%9.1fs | %5.1f%% in %02dh%02dm%04.1fs : ETA   %02dh%02dm%04.1fs    \r", 
            ch[ ++ic % nc ], t, ETA.ratio_done * 100.0,
            done.h,
            done.m,
            done.s,
            left.h,
            left.m,
            left.s
            );
    fflush( stderr );
}

int main( int argc, char *argv[] )
{
    const char *progname = _vfs:: get_base_name(argv[0]);
    try 
    {
        
        ////////////////////////////////////////////////////////////////////////
        //
        // check arguments
        //
        ////////////////////////////////////////////////////////////////////////
        if( argc < 5 )
            throw exception("usage: %s config.lua dt tmax output_file", progname );
        const string cfgfile( argv[1] );
        
        const double dt     = strconv::to_real<double>( argv[2], "dt" );
        const double tmax   = strconv::to_real<double>( argv[3], "tmax" );  
        const string output = argv[4];
        
        ////////////////////////////////////////////////////////////////////////
        //
        // create data
        //
        ////////////////////////////////////////////////////////////////////////
        Cell cell(cfgfile);
        
        std::cerr << "cell.sol: @pH=" << cell.sol.pH() << std::endl;
        std::cerr << cell.sol << std::endl;
        
        std::cerr << "cell.out: @pH=" << cell.outside(0).pH() << std::endl;
        std::cerr << cell.outside(0) << std::endl;
        
        
        ////////////////////////////////////////////////////////////////////////
        //
        // compute steady and/or set steady
        //
        ////////////////////////////////////////////////////////////////////////
        std::cerr << "ComputeSteadyEm" << std::endl;
        cell.ComputeSteadyEm();
        
        {
            lua_State *L = cell();
            lua_settop(L, 0);
            lua_getglobal(L, "Em");
            if( lua_isnumber(L, -1) )
            {
                const double Eini = lua_tonumber(L, -1);
                std::cerr << "Detect user's Em=" << Eini << std::endl;
                cell.EnforceSteadyEm( Eini );
                cell.RescaleEffectors();
            }
        }
        
        cell.TryRescaleAllRates();
        
        ////////////////////////////////////////////////////////////////////////
        //
        // extra information
        //
        ////////////////////////////////////////////////////////////////////////
        const double inside_intrinsic  = cell.inside(0).sum_zC();
        const double outside_intrinsic = cell.outside(0).sum_zC();
        const double delta_intrinsic = inside_intrinsic - outside_intrinsic;
        const double delta_global    = cell.Capa * cell.Em / ( cell.liters * __Faraday__ );
        const double extraConc = delta_global - delta_intrinsic;
        std::cerr << "extraConc=" << extraConc << " mol/L" << std::endl;
        
        
        
        
        ////////////////////////////////////////////////////////////////////////
        //
        // prepare to run
        //
        ////////////////////////////////////////////////////////////////////////
        const size_t num_iter = max_of<size_t>(1,ceil(tmax/dt));
        double       t = 0;
        eta          ETA;
        const double iter_factor = 1.0 / num_iter;
        //----------------------------------------------------------------------
        // save at 0
        //----------------------------------------------------------------------
        save_header(cell, output );
        save_values(cell, output, t);
        
        //----------------------------------------------------------------------
        // run
        //----------------------------------------------------------------------
        std::cerr.flush();
        ETA.reset();
        double tmx_old = -1;
        for( unsigned iter=1; iter <= num_iter; ++iter )
        {
            const double t_old = (iter-1) * dt;
            t                  =  iter    * dt;
            cell.Step(t_old, t);
            save_values(cell, output, t);
            const double tmx_now = ETA.now();
            if( tmx_now - tmx_old >= 0.1 )
            {
                ETA(iter*iter_factor);
                progress_callback(ETA,t);
                tmx_old = tmx_now;
            }
        }
        ETA(1.0);
        progress_callback(ETA,t);
        fflush( stderr );
        std::cerr << std::endl;
        
    }
    catch( const exception &e )
    {
        std::cerr << e.what() << std::endl;
        std::cerr << e.when() << std::endl;
        return -1;
    }
    catch(...)
    {
        std::cerr << "Unhandled exception in " << progname << std::endl;
        return -1;
    }
    
    
    return 0;
}